<template>
  <div class="anti-aliasing">
    <h1>抗锯齿</h1>
    <p>这个示例展示了WebGL中的抗锯齿后期处理效果。通过FXAA(Fast Approximate Anti-Aliasing)算法来减少图像中的锯齿现象，提高渲染质量。</p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>抗锯齿演示</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="demoCanvas" width="400" height="300"></canvas>
        <div class="control-panel">
          <el-form :model="aaForm" label-width="120px">
            <el-form-item label="抗锯齿算法">
              <el-select v-model="aaForm.algorithm" @change="updateAntiAliasing">
                <el-option label="FXAA" value="fxaa" />
                <el-option label="简单平滑" value="simple" />
                <el-option label="双线性" value="bilinear" />
                <el-option label="无抗锯齿" value="none" />
              </el-select>
            </el-form-item>
            <el-form-item label="质量等级">
              <el-slider
                v-model="aaForm.quality"
                :min="1"
                :max="5"
                :step="1"
                @input="updateAntiAliasing"
              />
            </el-form-item>
            <el-form-item label="边缘阈值">
              <el-slider
                v-model="aaForm.edgeThreshold"
                :min="0.01"
                :max="0.5"
                :step="0.01"
                @input="updateAntiAliasing"
              />
            </el-form-item>
            <el-form-item label="亮度阈值">
              <el-slider
                v-model="aaForm.lumaThreshold"
                :min="0.01"
                :max="0.3"
                :step="0.01"
                @input="updateAntiAliasing"
              />
            </el-form-item>
            <el-form-item label="启用抗锯齿">
              <el-switch v-model="aaForm.enableAA" @change="updateAntiAliasing" />
            </el-form-item>
          </el-form>
          <p>这是一个抗锯齿后期处理效果，你可以：</p>
          <ul>
            <li>选择不同的抗锯齿算法</li>
            <li>调整质量等级</li>
            <li>设置边缘检测阈值</li>
            <li>控制亮度检测阈值</li>
          </ul>
        </div>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>示例代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code>
// 顶点着色器
{{ VSHADER_SOURCE }}

// 片元着色器
{{ FSHADER_SOURCE }}
        </code></pre>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive, onUnmounted } from 'vue'
import { WebGLUtils } from '@/utils/webgl'

const demoCanvas = ref<HTMLCanvasElement | null>(null)
let gl: WebGLRenderingContext | null = null
let program: WebGLProgram | null = null
let webglUtils: WebGLUtils | null = null
let animationId: number | null = null
let texture: WebGLTexture | null = null

const aaForm = reactive({
  algorithm: 'fxaa',
  quality: 3,
  edgeThreshold: 0.125,
  lumaThreshold: 0.05,
  enableAA: true
})

// 顶点着色器
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  attribute vec2 a_TexCoord;
  varying vec2 v_TexCoord;
  void main() {
    gl_Position = a_Position;
    v_TexCoord = a_TexCoord;
  }
`

// 片元着色器
const FSHADER_SOURCE = `
  precision mediump float;
  uniform sampler2D u_Texture;
  uniform vec2 u_TextureSize;
  uniform int u_Algorithm; // 0: fxaa, 1: simple, 2: bilinear, 3: none
  uniform int u_Quality;
  uniform float u_EdgeThreshold;
  uniform float u_LumaThreshold;
  uniform bool u_EnableAA;
  varying vec2 v_TexCoord;

  // 计算亮度
  float getLuma(vec3 color) {
    return dot(color, vec3(0.299, 0.587, 0.114));
  }

  // FXAA算法
  vec3 fxaa(sampler2D tex, vec2 uv, vec2 texelSize) {
    vec3 colorCenter = texture2D(tex, uv).rgb;

    // 采样周围像素
    vec3 colorN = texture2D(tex, uv + vec2(0.0, -texelSize.y)).rgb;
    vec3 colorS = texture2D(tex, uv + vec2(0.0, texelSize.y)).rgb;
    vec3 colorE = texture2D(tex, uv + vec2(texelSize.x, 0.0)).rgb;
    vec3 colorW = texture2D(tex, uv + vec2(-texelSize.x, 0.0)).rgb;

    // 计算亮度
    float lumaCenter = getLuma(colorCenter);
    float lumaN = getLuma(colorN);
    float lumaS = getLuma(colorS);
    float lumaE = getLuma(colorE);
    float lumaW = getLuma(colorW);

    // 计算亮度范围
    float lumaMin = min(lumaCenter, min(min(lumaN, lumaS), min(lumaE, lumaW)));
    float lumaMax = max(lumaCenter, max(max(lumaN, lumaS), max(lumaE, lumaW)));
    float lumaRange = lumaMax - lumaMin;

    // 如果亮度变化太小，不需要抗锯齿
    if (lumaRange < max(u_EdgeThreshold, lumaMax * u_LumaThreshold)) {
      return colorCenter;
    }

    // 计算边缘方向
    float lumaNW = getLuma(texture2D(tex, uv + vec2(-texelSize.x, -texelSize.y)).rgb);
    float lumaNE = getLuma(texture2D(tex, uv + vec2(texelSize.x, -texelSize.y)).rgb);
    float lumaSW = getLuma(texture2D(tex, uv + vec2(-texelSize.x, texelSize.y)).rgb);
    float lumaSE = getLuma(texture2D(tex, uv + vec2(texelSize.x, texelSize.y)).rgb);

    float lumaDownUp = lumaS + lumaN;
    float lumaLeftRight = lumaE + lumaW;

    float lumaLeftCorners = lumaNW + lumaSW;
    float lumaDownCorners = lumaSW + lumaSE;
    float lumaRightCorners = lumaNE + lumaSE;
    float lumaUpCorners = lumaNW + lumaNE;

    bool isHorizontal = abs(-2.0 * lumaCenter + lumaDownUp) >= abs(-2.0 * lumaCenter + lumaLeftRight);

    float luma1 = isHorizontal ? lumaS : lumaE;
    float luma2 = isHorizontal ? lumaN : lumaW;

    float gradient1 = luma1 - lumaCenter;
    float gradient2 = luma2 - lumaCenter;

    bool is1Steepest = abs(gradient1) >= abs(gradient2);
    float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));

    float stepLength = isHorizontal ? texelSize.y : texelSize.x;
    float lumaLocalAverage = 0.0;

    if (is1Steepest) {
      stepLength = -stepLength;
      lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
    } else {
      lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
    }

    vec2 currentUv = uv;
    if (isHorizontal) {
      currentUv.y += stepLength * 0.5;
    } else {
      currentUv.x += stepLength * 0.5;
    }

    // 简化的FXAA实现
    vec2 offset = isHorizontal ? vec2(texelSize.x, 0.0) : vec2(0.0, texelSize.y);
    vec3 rgbA = 0.5 * (texture2D(tex, currentUv - offset).rgb + texture2D(tex, currentUv + offset).rgb);

    return rgbA;
  }

  // 简单平滑
  vec3 simpleSmooth(sampler2D tex, vec2 uv, vec2 texelSize) {
    vec3 color = texture2D(tex, uv).rgb;
    color += texture2D(tex, uv + vec2(texelSize.x, 0.0)).rgb;
    color += texture2D(tex, uv + vec2(-texelSize.x, 0.0)).rgb;
    color += texture2D(tex, uv + vec2(0.0, texelSize.y)).rgb;
    color += texture2D(tex, uv + vec2(0.0, -texelSize.y)).rgb;
    return color * 0.2;
  }

  // 双线性插值
  vec3 bilinearSmooth(sampler2D tex, vec2 uv, vec2 texelSize) {
    vec3 color = texture2D(tex, uv).rgb * 4.0;
    color += texture2D(tex, uv + vec2(texelSize.x, 0.0)).rgb * 2.0;
    color += texture2D(tex, uv + vec2(-texelSize.x, 0.0)).rgb * 2.0;
    color += texture2D(tex, uv + vec2(0.0, texelSize.y)).rgb * 2.0;
    color += texture2D(tex, uv + vec2(0.0, -texelSize.y)).rgb * 2.0;
    color += texture2D(tex, uv + vec2(texelSize.x, texelSize.y)).rgb;
    color += texture2D(tex, uv + vec2(-texelSize.x, texelSize.y)).rgb;
    color += texture2D(tex, uv + vec2(texelSize.x, -texelSize.y)).rgb;
    color += texture2D(tex, uv + vec2(-texelSize.x, -texelSize.y)).rgb;
    return color / 16.0;
  }

  void main() {
    if (!u_EnableAA) {
      gl_FragColor = texture2D(u_Texture, v_TexCoord);
      return;
    }

    vec2 texelSize = 1.0 / u_TextureSize;
    vec3 color;

    if (u_Algorithm == 0) {
      // FXAA
      color = fxaa(u_Texture, v_TexCoord, texelSize);
    } else if (u_Algorithm == 1) {
      // Simple smooth
      color = simpleSmooth(u_Texture, v_TexCoord, texelSize);
    } else if (u_Algorithm == 2) {
      // Bilinear smooth
      color = bilinearSmooth(u_Texture, v_TexCoord, texelSize);
    } else {
      // No anti-aliasing
      color = texture2D(u_Texture, v_TexCoord).rgb;
    }

    gl_FragColor = vec4(color, 1.0);
  }
`

function initVertexBuffers(gl: WebGLRenderingContext): boolean {
  // 全屏四边形
  const vertices = new Float32Array([
    -1.0, -1.0, 0.0, 0.0,
     1.0, -1.0, 1.0, 0.0,
    -1.0,  1.0, 0.0, 1.0,
     1.0,  1.0, 1.0, 1.0
  ])

  const buffer = gl.createBuffer()
  if (!buffer) {
    console.error('Failed to create buffer')
    return false
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

  const a_Position = gl.getAttribLocation(program!, 'a_Position')
  const a_TexCoord = gl.getAttribLocation(program!, 'a_TexCoord')

  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 16, 0)
  gl.enableVertexAttribArray(a_Position)

  gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 16, 8)
  gl.enableVertexAttribArray(a_TexCoord)

  return true
}

function createTexture(gl: WebGLRenderingContext): WebGLTexture | null {
  const texture = gl.createTexture()
  if (!texture) return null

  // 创建一个包含锯齿的图像用于抗锯齿演示
  const canvas = document.createElement('canvas')
  canvas.width = 256
  canvas.height = 256
  const ctx = canvas.getContext('2d')!

  // 白色背景
  ctx.fillStyle = '#ffffff'
  ctx.fillRect(0, 0, 256, 256)

  // 绘制一些有锯齿的几何图形
  ctx.fillStyle = '#000000'
  ctx.strokeStyle = '#000000'
  ctx.lineWidth = 1

  // 斜线（容易产生锯齿）
  ctx.beginPath()
  ctx.moveTo(50, 50)
  ctx.lineTo(150, 100)
  ctx.lineTo(200, 150)
  ctx.lineTo(100, 200)
  ctx.stroke()

  // 旋转的矩形
  ctx.save()
  ctx.translate(128, 128)
  ctx.rotate(Math.PI / 6)
  ctx.fillRect(-30, -30, 60, 60)
  ctx.restore()

  // 圆形（边缘可能有锯齿）
  ctx.beginPath()
  ctx.arc(80, 180, 25, 0, Math.PI * 2)
  ctx.stroke()

  // 三角形
  ctx.beginPath()
  ctx.moveTo(180, 80)
  ctx.lineTo(220, 120)
  ctx.lineTo(140, 120)
  ctx.closePath()
  ctx.fill()

  // 添加一些细线
  ctx.lineWidth = 0.5
  for (let i = 0; i < 10; i++) {
    ctx.beginPath()
    ctx.moveTo(20 + i * 20, 20)
    ctx.lineTo(40 + i * 20, 40)
    ctx.stroke()
  }

  gl.bindTexture(gl.TEXTURE_2D, texture)
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) // 使用最近邻采样以保持锯齿
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

  return texture
}

function updateAntiAliasing() {
  if (!gl || !program) return

  const u_Algorithm = gl.getUniformLocation(program, 'u_Algorithm')
  const u_Quality = gl.getUniformLocation(program, 'u_Quality')
  const u_EdgeThreshold = gl.getUniformLocation(program, 'u_EdgeThreshold')
  const u_LumaThreshold = gl.getUniformLocation(program, 'u_LumaThreshold')
  const u_EnableAA = gl.getUniformLocation(program, 'u_EnableAA')

  const algorithmMap: { [key: string]: number } = {
    'fxaa': 0,
    'simple': 1,
    'bilinear': 2,
    'none': 3
  }

  gl.uniform1i(u_Algorithm, algorithmMap[aaForm.algorithm])
  gl.uniform1i(u_Quality, aaForm.quality)
  gl.uniform1f(u_EdgeThreshold, aaForm.edgeThreshold)
  gl.uniform1f(u_LumaThreshold, aaForm.lumaThreshold)
  gl.uniform1i(u_EnableAA, aaForm.enableAA ? 1 : 0)

  draw()
}

function draw() {
  if (!gl || !program) return

  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}

function animate() {
  draw()
  animationId = requestAnimationFrame(animate)
}

function init() {
  if (!demoCanvas.value) return

  gl = demoCanvas.value.getContext('webgl')
  if (!gl) {
    console.error('WebGL not supported')
    return
  }

  webglUtils = new WebGLUtils(gl)
  // 创建着色器
  const vertexShader = webglUtils.createShader(gl.VERTEX_SHADER, VSHADER_SOURCE)
  const fragmentShader = webglUtils.createShader(gl.FRAGMENT_SHADER, FSHADER_SOURCE)
  if (!vertexShader || !fragmentShader) return
  program = webglUtils.createProgram(vertexShader, fragmentShader)
  if (!program) {
    console.error('Failed to create program')
    return
  }

  gl.useProgram(program)

  if (!initVertexBuffers(gl)) {
    console.error('Failed to initialize vertex buffers')
    return
  }

  texture = createTexture(gl)
  if (!texture) {
    console.error('Failed to create texture')
    return
  }

  // 设置uniform变量
  const u_Texture = gl.getUniformLocation(program, 'u_Texture')
  const u_TextureSize = gl.getUniformLocation(program, 'u_TextureSize')

  gl.uniform1i(u_Texture, 0)
  gl.uniform2f(u_TextureSize, demoCanvas.value.width, demoCanvas.value.height)

  gl.activeTexture(gl.TEXTURE0)
  gl.bindTexture(gl.TEXTURE_2D, texture)

  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.viewport(0, 0, demoCanvas.value.width, demoCanvas.value.height)

  updateAntiAliasing()
  animate()
}

onMounted(() => {
  init()
})

onUnmounted(() => {
  if (animationId) {
    cancelAnimationFrame(animationId)
  }
})
</script>

<style scoped>
.anti-aliasing {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

h1 {
  color: #303133;
  margin-bottom: 20px;
}

p {
  line-height: 1.6;
  color: #606266;
  margin-bottom: 30px;
}

.demo-section {
  margin-bottom: 30px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.demo-content {
  display: flex;
  gap: 30px;
  align-items: flex-start;
}

canvas {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
}

.control-panel {
  flex: 1;
  min-width: 300px;
}

.control-panel p {
  margin: 20px 0 10px;
  font-weight: bold;
  color: #409EFF;
}

.control-panel ul {
  list-style-type: disc;
  padding-left: 20px;
  color: #606266;
}

.control-panel li {
  margin: 5px 0;
}

.code-section {
  margin-top: 30px;
}

.code-content {
  background-color: #f5f7fa;
  border-radius: 4px;
  overflow-x: auto;
}

.code-content pre {
  margin: 0;
  padding: 20px;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.5;
  color: #303133;
}
</style>
